#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>

#include "common.h"
#include "hash_table.h"
#include "SPAN_functions.h"

#define MAX_COUNTERS        256
#define MAX_NR_CPUS         32
#define PROC                "/proc"

#define ERR_PERF_OPEN \
"SPAN_ERROR: sys_perf_event_open() syscall returned with %d (%s)\n"



pthread_mutex_t run_mutex        = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  condition_var    = PTHREAD_COND_INITIALIZER;
pthread_t thread1                = 0;
int	        sPause               = 0;

NODE **thread_list;

// internal usage functions
void ex_program(int);                              //handle ctrl+c
void close_all_counters();						   // close all the PMCs
void read_counter(int);                            // read PMCs once and store them in file fp
void run_perf_stat();                              // continuously read the PMCs
int create_perf_stat_counter(int*, int, int, int); // create the PMCs


void read_counter(int counter)
{
	/*
	u64 single_count[3];

	size_t res;
	if (fd[counter] <= 0)
		return;
	res = read(fd[counter], single_count, 3 * sizeof(u64));
	if (fp == 0){
		printf("The PMC values are not written to a file");
		return;
	}

	if(res == 3 * sizeof(u64))
	{
		if (run_count  == 0)
			previous[counter] = single_count[0];
		else
			current[counter] = single_count[0];
		if ((counter == (tn-1))&&(run_count > 0))
		{
			int i=0;
			for (i=0;i < tn-1; i+=2)
			{
				float temp = ((float)(current[i] - previous[i])/(current[i+1] - previous[i+1]));
				//printf("%llu,%llu,%llu,%llu\n",current[i], previous[i],current[i+1],previous[i+1]);
				//printf("%f\n",temp);
				if (fp!=0)
					fprintf(fp, "%f,",temp);
				else
					printf("%f",temp);
			}
			memcpy (previous, current, tn);
			fprintf(fp, "\n");
		}
	}
	else
		fprintf(stderr, "Fail to read counter %d\n", counter);
	*/
}

void clean_up(){
	close_all_counters();
}


void close_all_counters(){
/*
	int counter;

	for (counter = 0; counter < tn; counter++)
	{
		if (fd[counter] <= 0)
			continue;

		close(fd[counter]);
		fd[counter] = -1;
		//printf("closing\n");
	}
	if(fp){
		fclose(fp);
		fp=0;
	}
	if(ap){
		fclose(ap);
		ap=0;
	}
	if(fd != 0){
		free(fd);
		fd=0;
	}
	if(previous != 0){
		free(previous);
		previous=0;
	}
	if(current != 0){
		free(current);
		current=0;
	}
	*/
}
/*
void run_perf_stat()
{

	int counter = 0;
	for (; counter < tn; counter++)
	{
		read_counter(counter);
	}
}
*/

void SPAN_initialize ()
{
	int i = 0;
	/* First, gather system information */
	int nr_cpus = sysconf(_SC_NPROCESSORS_ONLN); // the the number of CPU
	if (nr_cpus <= 0){
		clean_up();
		fprintf (stderr,"SPAN_ERROR: Cannot find the number of CPU in SPAN_initialize.\n");
	}

	/* allocate space for the hashtable */
	if((thread_list = (NODE **)malloc(HASHSIZE*sizeof(NODE*))) == NULL)
		perror("SPAN_ERROR: Failed to allocate\n");
	/* else, initialize the hash table */
	else
		for(i=0;i<HASHSIZE;i++)
			thread_list[i] = NULL;
/*
	nr_cpus = sysconf(_SC_NPROCESSORS_ONLN); // the the number of CPU
	if (nr_cpus <= 0){
		close_all_counters();
		fprintf (stderr,"SPAN_ERROR: Cannot find the number of CPU in SPAN_initialize ().\n");
		return;
	}
	// initialize the file descriptor
	fd       = (int* )malloc ( tn*sizeof(int) );
	previous = (u64* )malloc ( tn*sizeof(u64) );
	current  = (u64* )malloc ( tn*sizeof(u64) );

	// open the output for PMC
	if ((fp = fopen("SPAN.out", "a+")))
		;//printf("Success opening SPAN.OUT\n");
	else
		fprintf (stderr,"SPAN_ERROR: Error opening SPAN.txt in SPAN_initialize ().\n");

	// open the output for program
	if ((ap = fopen("SPAN_program.out", "a+")))
		;//printf("Success opening SPAN_program\n");
	else
		fprintf (stderr,"SPAN_ERROR: Error opening sample_program.txt in SPAN_initialize ().\n");

	//start create counters
	int counter = 0;
	int i = 0;
	for (;i < nr_cpus;i++)
	{
		create_perf_stat_counter(counter, pid, i, 0);
		if(fd[counter] != -1)
			counter++;
		create_perf_stat_counter(counter, pid, i, 1);
		if(fd[counter] != -1)
			counter++;
	}
	*/
}

void SPAN_pause()
{
	//pthread_mutex_lock( &run_mutex );
}

void SPAN_continue()
{
	//pthread_mutex_unlock( &run_mutex );
}
void SPAN_start(char * function1)
{

/* unit test for hash_table */

	NODE *np;
	NODE_D data;

	char pid_tt[] = "123";
	np = NULL;
	memset(&data,0,sizeof(NODE_D));

	np = search (pid_tt, thread_list);
	if(!np)
		fprintf(stdout,"cannot find it\n");

	data.t_tid = pid_tt;

	if(insert(&data, thread_list) != NULL)
	{
		fprintf(stdout,"inserted\n");
		np = search ("123", thread_list);
		if(np)
			fprintf(stdout,"find it, the previous is %d\n",np->data.p_tid);
	}
	else
		fprintf(stdout, "failed to insert\n");

	data.p_tid = 9;


	if(insert(&data, thread_list) != NULL)
	{
		fprintf(stdout,"inserted\n");
		np = search ("123", thread_list);
		if(np)
			fprintf(stdout,"find it, the previous is %d\n",np->data.p_tid);
	}
	else
		fprintf(stdout, "failed to insert\n");

	if(delete("123", thread_list) != -1)
		fprintf(stdout,"deleted\n");
	else
		fprintf(stdout, "failed to delete\n");

	np = search (pid_tt, thread_list);
	if(!np)
		fprintf(stdout,"cannot find it\n");



	strncpy(pid_tt,"221",strlen(pid_tt));
	data.t_tid = pid_tt;

	if(insert(&data, thread_list) != NULL)
	{
		fprintf(stdout,"inserted\n");
		np = search ("221", thread_list);
		if(np)
			fprintf(stdout,"find it, the previous is %d\n",np->data.p_tid);
	}
	else
		fprintf(stdout, "failed to insert\n");

	strncpy(pid_tt,"321",strlen(pid_tt));
	data.t_tid = pid_tt;

	if(insert(&data, thread_list) != NULL)
	{
		fprintf(stdout,"inserted\n");
		np = search ("321", thread_list);
		if(np)
			fprintf(stdout,"find it, the previous is %d\n",np->data.p_tid);
	}
	else
		fprintf(stdout, "failed to insert\n");


	if(delete("221", thread_list) != -1)
		fprintf(stdout,"deleted\n");
	else
		fprintf(stdout, "failed to delete\n");

	if(delete("sadf", thread_list) != -1)
		fprintf(stdout,"deleted\n");
	else
		fprintf(stdout, "failed to delete\n");


	np = search ("221", thread_list);
	if(!np)
		fprintf(stdout,"cannot find it\n");



/*
	NODE *np;
	int i;
	NODE_D data;
	char tid_str_t[1+8*sizeof(pid_t)];
	pid_t tid = (pid_t)syscall(224);
	pthread_t thread   = 0;

	if(!sprintf(tid_str_t,"%d",tid)){
		perror("SPAN_ERROR: failed to use sprintf\n");
		return;
	}

	if((np = search(tid_str_t, thread_list)) == NULL){
		if((data.fd = fopen(tid_str_t,"a+")) == NULL){
			perror("SPAN_ERROR: failed to open file to write to\n");
			return;
		}
		for (i = 0;i < NR_COUNTERS;i++)
			if(create_perf_stat_counter(&data.perf_fd[i], tid, -1, i) < 0)
                fprintf(stderr, ERR_PERF_OPEN, data.perf_fd[i], strerror(errno));


		if (pthread_create(&thread, NULL, (void* (*)(void*))SPAN_run, (void *) function1) != 0)
			fprintf (stderr,"SPAN_ERROR: failed to create thread in SPAN_start ().\n");
	}
*/
	/*
	//get the current Process ID
	int tid = (long int)syscall(224);
#ifdef _DEBUG
	fprintf(stderr, "Tid %d\n", tid);
#endif
	run_count = 0;  // reset the run_count
	sPause    = 0; // reset the pause
	thread1   = 0; // reset thread1
	if (pthread_create(&thread1, NULL, (void* (*)(void*))SPAN_run, (void *) function1) != 0)
		fprintf (stderr,"SPAN_ERROR: failed to create thread in SPAN_start ().\n");;
		*/
}

void SPAN_stop()
{
	clean_up_table(thread_list);
	/*
	while(1)
	{
		pthread_mutex_lock( &run_mutex );
		pthread_cond_wait( &condition_var, &run_mutex );
		sPause = 1;
		pthread_mutex_unlock( &run_mutex );
		break;
	}
	pthread_join( thread1, NULL);
	 */
}


void SPAN_run(void *function)
{
	/*
	struct timespec tim, tim2;
	tim.tv_sec = 0;
	tim.tv_nsec = 100000000;
	//    (void) signal(SIGINT, ex_program);

	while (!sPause)
	{
		pthread_mutex_lock( &run_mutex );
		run_perf_stat();
		run_count++;
		pthread_cond_signal( &condition_var );
		pthread_mutex_unlock( &run_mutex );
		nanosleep(&tim , &tim2);
	}
	if (ap){
		fprintf(ap, (char *) function);
		fprintf(ap, "\n%d\n",run_count-1);
	}
	else
		fprintf (stderr,"SPAN_ERROR: failed to write to sample_program.txt in SPAN_run ().\n");
	close_all_counters();
	*/
}

void ex_program(int sig)
{
	SPAN_stop();
	printf("Wake up call ... !!! - Catched signal: %d ... !!\n", sig);
	(void) signal(SIGINT, SIG_DFL);
	//    close_all_counters();
}
